package cn.chendd.example.jpa.nativequery;

import cn.chendd.example.jpa.nativequery.annotions.Query;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.apache.commons.lang3.ArrayUtils;
import org.hibernate.query.internal.NativeQueryImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.repository.query.Param;

import javax.persistence.EntityManager;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author chendd
 * @date 2020/5/6 22:47
 */
public abstract class SQLQuery {

    private EntityManager entityManager;
    private Query query;
    private Method method;
    private Object args[];
    private Logger logger = LoggerFactory.getLogger(getClass());

    Object doExecute(Object[] args){
        SqlParameter sqlParameter = this.reloadSql(this.query.sql(), args);
        return this.execute(sqlParameter);
    }

    private SqlParameter reloadSql(String sql, Object[] args) {
        sql = InnerVariable.reloadVariable(sql);
        if(args == null) {
            return new SqlParameter(sql);
        }
        List<Object> paramList = Lists.newArrayList();
        Matcher matcher = Pattern.compile("\\:([\\a-zA-Z_\\$][\\w\u4e00-\u9fa5]*)" , Pattern.CASE_INSENSITIVE).matcher(sql);
        int matchCount = matcher.groupCount();
        matcher.reset();
        //当参数中不含有@Param注解的时候，检查参数个数与正则个数是否一致，执行参数一致时的解析逻辑
        Map<String , Object> methodParameters = SQLQuery.getMethodParameterValue(this.method , args);
        if(methodParameters.isEmpty() && ArrayUtils.isNotEmpty(args)){
            throw new UnsupportedOperationException("Method must use @param for all parameters");
        }
        while(matcher.find()){
            String paramName = matcher.group();
            String propName = matcher.group(1);
            Object arg = methodParameters.get(propName);
            if(arg == null){
                sql = this.fillEmpty(sql , paramList , paramName);
                continue;
            }
            sql = fillParameters(sql , paramList , paramName , arg);
        }
        return new SqlParameter(sql , paramList);
    }

    private String fillEmpty(String sql , List<Object> paramList , String paramName){
        sql = sql.replaceFirst(paramName , "?");
        paramList.add(null);
        return sql;
    }

    private String fillParameters(String sql, List<Object> paramList, String paramName, Object arg) {
        //参数为空的处理
        boolean isArray = arg != null && arg.getClass().isArray();
        if(isArray) {
            StringBuilder paramBuilder = new StringBuilder();
            Object arrays[] = (Object[]) arg;
            if(arrays.length == 0){
                return this.fillEmpty(sql , paramList , paramName);
            }
            for (Object array : arrays) {
                paramBuilder.append(",?").append(array);
                paramList.add(array);
            }
            sql = sql.replaceFirst(paramName , paramBuilder.deleteCharAt(0).toString());
        } else if (arg instanceof List){
            StringBuilder paramBuilder = new StringBuilder();
            List<?> list = (List<?>) arg;
            if(list.isEmpty()){
                return sql = this.fillEmpty(sql , paramList , paramName);
            }
            for (Object array : list) {
                paramBuilder.append(",?");
                paramList.add(array);
            }
            sql = sql.replaceFirst(paramName , paramBuilder.deleteCharAt(0).toString());
        } else {
            sql = sql.replaceFirst(paramName , "?");
            paramList.add(arg);
        }
        return sql;
    }

    protected Object queryResult(NativeQueryImpl nativeQuery) {
        Class<?> returnType = this.getMethod().getReturnType();
        if (List.class.isAssignableFrom(returnType)){
            //当返回结果为List类型时
            return nativeQuery.getResultList();
        } else {
            //当返回结果不为List时，如：Map类型、Javabean类型
            return nativeQuery.getSingleResult();
        }
    }

    public static Map<String , Object> getMethodParameterValue(Method method , Object argsValue[]) {

        Map<String , Object> map = Maps.newLinkedHashMap();
        Parameter[] parameters = method.getParameters();
        for (int i = 0; i < parameters.length; i++) {
            Parameter parameter = parameters[i];
            Param param = parameter.getAnnotation(Param.class);
            if(param == null){
                continue;
            }
            map.put(param.value() ,argsValue[i]);
        }
        return map;
    }

    protected abstract Object execute(SqlParameter sqlParameter);

    public EntityManager getEntityManager() {
        return entityManager;
    }

    public void setEntityManager(EntityManager entityManager) {
        this.entityManager = entityManager;
    }

    public Object[] getArgs() {
        return args;
    }

    public void setArgs(Object[] args) {
        this.args = args;
    }

    public Query getQuery() {
        return query;
    }

    public void setQuery(Query query) {
        this.query = query;
    }

    public Method getMethod() {
        return method;
    }

    public void setMethod(Method method) {
        this.method = method;
    }

}
